<!DOCTYPE html>
<!--
/*
 *
 * Copyright (C) 2012 Pierre-Michel Ricordel All Rights Reserved.
 *
 */
 -->
 
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>LOCOMOTIVE</title>
<style>
body, html {
  margin: 0px;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#framerate {
  position: absolute;
  top: 10px;
  left: 10px;
  background-color: rgba(0,0,0,0.3);
  padding: 1em;
  color: white;
}
#locomotive {
  width: 100%;
  height: 100%;
}
</style>


<script type="text/javascript" src="webgl-utils.js"></script>
<script type="text/javascript" src="gl-matrix-min.js"> </script>
<script type="text/javascript" src="utils.js"> </script>

<script id="vshader" type="x-shader/x-vertex">
#version 100
uniform mat4 PVMMatrix;

attribute vec3 in_Position;

varying vec3 texCoord;

void main(void)
{
     gl_Position = PVMMatrix * vec4(in_Position, 1.0);
	 texCoord = in_Position+vec3(0.5, 0.5, 0.5); //cheating with texcoords - we add 0.5 because the model cube is centered
}
</script>

<script id="fshader" type="x-shader/x-fragment">
#version 100
precision mediump float;
uniform vec3 eyePosition;

varying vec3 texCoord;

//out vec4 out_Color;

uniform sampler2D terrainTex;
uniform sampler2D mainTex;
uniform sampler2D detailTex;
/*
void ray_intersect_stepping(
	in float hscale, 
	in sampler2D relief_map, // Relaxed cone map
	inout vec3 ray_pos, // Ray position
	in vec3 ray_dir,
	out vec3 debug) // Ray direction
{
	const int steps=8;
	const int bisteps=10;
	vec4 t;
	//t=texture2D(relief_map, ray_pos.xy);
	vec3 vec_step;
	//stepping
	debug=vec3(ray_pos);
	
	vec_step=(ray_dir)/float(steps);
	for (int i=0; i<steps; i++)
	{
		t=texture2D(relief_map, ray_pos.xy); //sample the heightmap
		if (t.x*hscale>ray_pos.z) 
		{
			//debug=vec3(float(i)/float(steps), t.x*hscale, ray_pos.z);
			break;
		}
		ray_pos=ray_pos+vec_step;
		if (ray_pos.x<=0.001 || ray_pos.y<=0.001 || ray_pos.x>=0.999 || ray_pos.y>=0.999) discard;
	}
	

	//binary search
	vec3 bs_range=0.5*vec_step;
	ray_pos-=bs_range;
	for (int i=0; i<bisteps; i++)
	{
		t=texture2D(relief_map, ray_pos.xy); //sample the heightmap
		bs_range*=0.5;
		if (t.x*hscale>ray_pos.z)
			ray_pos-=bs_range;
		else
			ray_pos+=bs_range;
	}
	
}
*/
/*

void ray_intersect_relaxedcone(float hscale, 
	sampler2D relief_map, // Relaxed cone map
	inout vec3 ray_pos, // Ray position
	vec3 ray_dir // Ray direction
	)
{
	const int cone_steps = 41;
	const int binary_steps = 10;

	float s=sign(ray_dir.z);

	ray_dir /= ray_dir.z; // Scale ray_dir

	float ray_ratio = s*length(ray_dir.xy);

	vec3 pos = ray_pos;
	for( int i=0; i<cone_steps; i++)
	{
		vec4 tex = texture2D(relief_map, pos.xy);
		float cone_ratio = -tex.y/hscale;
		float height = clamp(pos.z-tex.x*hscale, 0.0, 1.0);
		float d = cone_ratio*height/(ray_ratio-s*cone_ratio);
		pos -= ray_dir * d;
		//early discard is very efficient
		if (pos.x<=0.001 || pos.y<=0.001 || pos.x>=0.999 || pos.y>=0.999 || pos.z>hscale) discard;
	}
	
	// Binary search initial range and initial position
	vec3 bs_range = -0.5*(ray_pos.z-pos.z)*ray_dir;
	vec3 bs_position = ray_pos + bs_range;

	for( int i=0; i<binary_steps; i++ )
	{
		vec4 tex = texture2D(relief_map, bs_position.xy);
		bs_range *= 0.5;
		if (bs_position.z > tex.x*hscale) // If outside
			bs_position += bs_range; // Move forward
		else
			bs_position -= bs_range; // Move backward
	}
	ray_pos=bs_position;
	
}
*/
void main(void)
{
	vec3 pt_bottom;
	vec3 vec_pos;
	vec3 ray_dir;

	const float hscale=0.1; //height ratio to width

	//we suppose we are inside the box
	pt_bottom=texCoord; //the bottom or the sides of the box

	//the ray is between eye_pos and pt_bottom !
	//start from the eye
	vec_pos=eyePosition;
	ray_dir=pt_bottom-eyePosition;

	//if we are over max height, intersect with max height box
	if (vec_pos.z>hscale)
	{
		if (ray_dir.z>0.0) discard; //upward and over the box
		vec_pos+=ray_dir*((eyePosition.z-hscale)/eyePosition.z);
	}

	//vec3 debug=vec3(1.0, 0.0, 1.0);
//	ray_intersect_stepping(hscale, terrainTex, vec_pos, pt_bottom-vec_pos, debug);
//	ray_intersect_relaxedcone(hscale, terrainTex, vec_pos, pt_bottom-vec_pos);

	vec3 vec_step=ray_dir/8.0;
	for (int i=0; i<8; i++)
	{
		vec4 t=texture2D(terrainTex, vec_pos.xy); //sample the heightmap
		if (t.x*hscale>vec_pos.z) 
		{
			//debug=vec3(float(i)/float(steps), t.x*hscale, ray_pos.z);
			break;
		}
		vec_pos=vec_pos+vec_step;
		if (vec_pos.x<=0.001 || vec_pos.y<=0.001 || vec_pos.x>=0.999 || vec_pos.y>=0.999) discard;
	}

	//float dist=length(eyePosition-vec_pos);
	//vec4 dmod=0.5*(texture2D(detailTex, 45.0*vec_pos.xy)-0.5);
	//gl_FragColor=texture2D(mainTex, vec_pos.xy)+clamp(1.0-dist*5.0, 0.0, 1.0)*dmod;

	//gl_FragColor=vec4(debug, 1.0);
	gl_FragColor=vec4(eyePosition.z, vec_pos.z, hscale, 1.0);
//	gl_FragColor=texture2D(mainTex, vec_pos.xy);
	
	//float dist=length(eyePosition-vec_pos); //in model space !
	//compute depth
	//NOPE: this is not possible on OpenGL ES !
//	vec4 clipPos = PVMMatrix * vec4(vec_pos-vec3(0.5, 0.5, 0.5), 1.0);
//	float ndcDepth = clipPos.z / clipPos.w;
//	gl_FragDepth=(gl_DepthRange.diff * ndcDepth + gl_DepthRange.near + gl_DepthRange.far) / 2.0;
}

</script>

<script>
	//global state
    var g = {};

	function log(msg) {
		if (window.console && window.console.log) {
			window.console.log(msg);
		}
	}

    function init()
    {
        // Initialize
		var canvas = document.getElementById("locomotive");
		var gl = WebGLUtils.setupWebGL(canvas);

        if (!gl) {
          return;
        }
		
		// create our shaders
		var vertexShader = loadShader(gl, "vshader");
		var fragmentShader = loadShader(gl, "fshader");

		// Create the program object
		var program = gl.createProgram();

		// Attach our two shaders to the program
		gl.attachShader (program, vertexShader);
		gl.attachShader (program, fragmentShader);

		// Bind attributes (just position)
		gl.bindAttribLocation (program, 0, "vPosition");

		// Link the program
		gl.linkProgram(program);

		// Check the link status
		var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
		if (!linked && !gl.isContextLost()) {
			// something went wrong with the link
			var error = gl.getProgramInfoLog (program);
			log("Error in program linking:"+error);

			gl.deleteProgram(program);
			gl.deleteProgram(fragmentShader);
			gl.deleteProgram(vertexShader);

			return;
		}

		gl.useProgram(program);

		gl.clearColor(0.4, 0.6, 0.9, 1.0);
		gl.clearDepth(200);

		gl.enable(gl.DEPTH_TEST);
		gl.disable(gl.CULL_FACE);
//		gl.enable(gl.BLEND);
//		gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

        // Set some uniform variables for the shaders
        g.terrainLoc=gl.getUniformLocation(program, "terrainTex");
        g.mainLoc=gl.getUniformLocation(program, "mainTex");
        g.detailLoc=gl.getUniformLocation(program, "detailTex");

        // Create a box. On return 'gl' contains a 'box' property with
        // the BufferObjects containing the arrays for vertices,
        // normals, texture coords, and indices.
        g.box = makeBox(gl);

        // Load an image to use. Returns a WebGLTexture object
        g.terrainTexture = loadImageTexture(gl, "nhelensterrain.png");
        g.mainTexture = loadImageTexture(gl, "nhelens.jpg");
        g.detailTexture = loadImageTexture(gl, "detail.jpg");

        // Create some matrices to use later and save their locations in the shaders
        g.projMatrix = mat4.create();

        g.viewMatrix = mat4.create();
		mat4.lookAt([50.0, 10.0, 5.0], [50.0, 11.0, 5.0], [0, 0, 1], g.viewMatrix);

        g.modelMatrix = mat4.create();
		mat4.identity(g.modelMatrix);
		mat4.translate(g.modelMatrix, [50, 50, 50]);
		mat4.scale(g.modelMatrix, [100, 100, 100]);

        g.pvmMatrix = mat4.create();
		
		g.eyePos=vec3.create();
		//compute eye pos in model space
		var inv=mat4.create();
		mat4.inverse(g.modelMatrix, inv);
		mat4.multiplyVec3(inv, [50.0, 10.0, 5.0], g.eyePos);
		vec3.add(g.eyePos, [0.5, 0.5, 0.5]);
		
        g.pvmMatrixLoc = gl.getUniformLocation(program, "PVMMatrix");
        g.eyePosLoc = gl.getUniformLocation(program, "eyePosition");
		
	

        // Enable all of the vertex attribute arrays.
        gl.enableVertexAttribArray(0);

        // Set up all the vertex attributes for vertices
        gl.bindBuffer(gl.ARRAY_BUFFER, g.box.vertexObject);
        gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);

        // Bind the index array
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g.box.indexObject);

        return gl;
    }

    var requestId;

    function reshape(gl)
    {
        // change the size of the canvas's backing store to match the size it is displayed.
        var canvas = document.getElementById('locomotive');
        if (canvas.clientWidth == canvas.width && canvas.clientHeight == canvas.height)
            return;

        canvas.width = canvas.clientWidth;
        canvas.height = canvas.clientHeight;

        // Set the viewport and projection matrix for the scene
        gl.viewport(0, 0, canvas.clientWidth, canvas.clientHeight);
		mat4.perspective(60, canvas.clientWidth / canvas.clientHeight, 0.1, 200, g.projMatrix);
    }

    function drawPicture(gl)
    {
        // Make sure the canvas is sized correctly.
        reshape(gl);

        // Clear the canvas
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        // Make PVM matrix.
		mat4.multiply(g.projMatrix, g.viewMatrix, g.pvmMatrix);
		mat4.multiply(g.pvmMatrix, g.modelMatrix, g.pvmMatrix);
        gl.uniformMatrix4fv(g.pvmMatrixLoc, false, g.pvmMatrix);

		//push eye position in model space 
        gl.uniform3fv(g.eyePosLoc, g.eyePos);
		

        // Bind the texture to use
		gl.activeTexture(gl.TEXTURE0);
        gl.uniform1i(g.terrainLoc, 0);
        gl.bindTexture(gl.TEXTURE_2D, g.terrainTexture);

		gl.activeTexture(gl.TEXTURE1);
        gl.uniform1i(g.mainLoc, 1);
        gl.bindTexture(gl.TEXTURE_2D, g.mainTexture);

		gl.activeTexture(gl.TEXTURE2);
        gl.uniform1i(g.detailLoc, 2);
        gl.bindTexture(gl.TEXTURE_2D, g.detailTexture);

        // Draw the cube
        gl.drawArrays(gl.TRIANGLES, 0, g.box.numPoints);

        // Show the framerate
        framerate.snapshot();
    }

    function start()
    {
        var c = document.getElementById("locomotive");

        //c = WebGLDebugUtils.makeLostContextSimulatingCanvas(c);
        // tell the simulator when to lose context.
        //c.loseContextInNCalls(1);

        c.addEventListener('webglcontextlost', handleContextLost, false);
        c.addEventListener('webglcontextrestored', handleContextRestored, false);

        var gl = init();
        if (!gl) {
           return;
        }

        incAngle = 0.5;
        framerate = new Framerate("framerate");
        var f = function() {
            drawPicture(gl);
            requestId = window.requestAnimFrame(f, c);
        };
        f();

        function handleContextLost(e) {
            e.preventDefault();
            clearLoadingImages();
            if (requestId !== undefined) {
                window.cancelAnimFrame(requestId);
                requestId = undefined;
            }
        }

        function handleContextRestored() {
            init();
            f();
        }
    }
</script>
<style type="text/css">
    canvas {
        border: 2px solid black;
    }
</style>
</head>

<body onload="start()">
<canvas id="locomotive">
    If you're seeing this your web browser doesn't support the &lt;canvas>&gt; element. Ouch!
</canvas>
<div id="framerate"></div>
</body>

</html>
